//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <core.h>
#include <_hal.h>
#include <mantle.h>
#include "exception.h"
#include <eladbg.h>

#ifdef _neptune
#include <nu_api.h>
#endif

extern _ELASTOS Boolean g_bDbgStubEntry;
extern IDeviceDriver *g_pConsoleDev;

#if !defined(_RELEASE) || defined(_PRERELEASE)
extern IDeviceDriver *g_pDebugInfo;
#endif

//#ifndef _RELEASE
STATIC const char *s_szExceptionDescriptions[] = {
    "Undefined Instruction",
    "Prefetch Abort",
    "Data Abort"
};

STATIC INLINE const char *GetExceptionDescription(vector_t vector)
{
    assert(vector < sizeof(s_szExceptionDescriptions) / sizeof(const char *));

    return s_szExceptionDescriptions[vector];
}

//#endif // _RELEASE

typedef enum {
    ELADBG_STUB = 1,
    GDB_STUB,
    UNDEFINED_STUB
}DbgStubType;

static DbgStubType g_cDbgStubType = UNDEFINED_STUB;

STATIC DbgStubType SelectStubType(UInt32 uAddr)
{
#if (defined(_ELADBGAGENT) && defined(_UDBAGENT))
    CProcess *proc = GetCurrentProcess();
    ModuleView *pModuleView = NULL;
    if (NULL == proc) return GDB_STUB;
    _ELASTOS Boolean bIsValidAddr = proc->m_AS.IsValidAddress(uAddr);
    if (bIsValidAddr) {
        pModuleView = (ModuleView *)(proc->m_moduleList.Next());
        while (pModuleView != &(proc->m_moduleList)) {
            if ((uAddr >= pModuleView->m_pModule->m_uImageBase) && (uAddr < \
                pModuleView->m_pModule->m_uImageBase + pModuleView->m_pModule->m_uTotalSize)) {
                break;
            }
            pModuleView = (ModuleView *)(pModuleView->Next());
        }
    }
    else {
        pModuleView = proc->m_pMainModule;
    }
    //BUGBUG: Gnu app must be stripped before running in the target.
    if (pModuleView && pModuleView->m_pModule) {
        return (pModuleView->m_pModule->m_pImgDebugDirEntry ? ELADBG_STUB : GDB_STUB);
    }
    return GDB_STUB;

#elif (defined(_ELADBGAGENT) && !defined(_UDBAGENT))
    return ELADBG_STUB;
#elif (defined(_UDBAGENT) && !defined(_ELADBGAGENT))
    return GDB_STUB;
#else
    return UNDEFINED_STUB;
#endif
}

static bool s_bInitialized = false;
static bool s_bMonitoring = false;

EXTERN void AnsiToUnic(wchar_t *pwszName, const char *pszName);
EXTERN_C ECode _CPrivateProfile_GetAString(AString fileName, AString appName,
			AString keyName, AStringBuf *pReturnedString);

static bool IsMonitoringDebugBreak()
{
	if (!s_bInitialized) {
		ECode ec;
		AStringBuf_<_MAX_PATH> asFileName;
		AStringBuf_<_MAX_PATH> asAppName;
		AStringBuf_<_MAX_PATH> asKeyName;
		AStringBuf_<_MAX_PATH> asReturnString;

		asFileName.Copy("C:/elastos.cfg");
		asAppName.Copy("CompoundChannel");
		asKeyName.Copy("DebugMonitor");

		ec = _CPrivateProfile_GetAString(asFileName, asAppName, asKeyName, &asReturnString);
		s_bInitialized = true;

		if (ec == NOERROR && asReturnString.Compare("Enable") == 0) {
			s_bMonitoring = true;
		}
	}

	return s_bMonitoring;
}

EXTERN_C int CDECL _sprintf(char *buf, const char *fmt,...);

EXTERN_C print_t _print;
EXTERN_C void BspWriteConsole(const char *szMessage);
EXTERN void WriteToConsole(const char *szMessage);

/*
 * Handle debug event and return whether further TryToSendReceiveDebugMsg
 * instruction is needed or not.
 *
 * If configuration in elastos.cfg is set to enabling debug monitor, this
 * debugger handler will wait for eladbg.exe to attach this process and then
 * return false for another TryToSendReceiveDebugMsg call.
 */
extern bool CDECL DebuggerHandler(
                    vector_t vector,
                    InterruptContext *pContext,
                    uint_t uPageFaultAddr)
{
#if defined(ELASTOS_RC)
    BspReboot();
#else
    
    if (IS_IN_KERNEL_MODE(pContext)
            && _print != &BspWriteConsole && _print != &WriteToConsole) {
        _print = &BspWriteConsole;
    }
#if defined(_techfaith) //for TF: enable console for kprintf
    g_pConsoleDev->Control(0x0C, NULL_MEMORYBUF, NULL, NULL);
#endif //_techfaith

//#ifndef _RELEASE
    if (!g_bDbgStubEntry) {
        kprintf("*EXCEPTION* %s\n", GetExceptionDescription(vector));

#ifdef _neptune
        // kprintf("\n*************Nucleus Debug Info***********************\n");
        // nu_PrintMemoryInfo();

        // kprintf("\n*************Elastos Debug Info***********************\n");
#endif
        kprintf("r0  = %8x, r1  = %8x, r2  = %8x, r3  = %8x\n"
            "r4  = %8x, r5  = %8x, r6  = %8x, r7  = %8x\n"
            "r8  = %8x, r9  = %8x, r10 = %8x, r11 = %8x\n"
            "r12 = %8x, sp  = %8x, lr  = %8x, pc  = %8x\n"
            "cpsr= %8x\n",
            pContext->r0, pContext->r1, pContext->r2, pContext->r3,
            pContext->r4, pContext->r5, pContext->r6, pContext->r7,
            pContext->r8, pContext->r9, pContext->r10, pContext->r11,
            pContext->r12, pContext->sp, pContext->lr, pContext->pc,
            pContext->cpsr);

        switch (vector) {
        case ExceptionVector_DataAbort:
            kprintf("fault status: %x, fault address: %x\n",
                    GetFaultStatus(), GetFaultAddress());
            break;
        case ExceptionVector_PrefetchAbort:
            kprintf("fault address: %x\n", pContext->pc);
            break;
        }

        CProcess *proc = GetCurrentProcess();
        kprintf("Current Process: %x, Current Thread: %x [%S]\n",
                proc, GetCurrentThread(),
                GetCurrentThread()->m_wszName);

#if !defined(_RELEASE) || defined(_PRERELEASE)
        if (proc) {
            if(NULL != g_pDebugInfo){
                g_pDebugInfo->Control(0x10, NULL_MEMORYBUF, NULL, NULL);
            }
        }
#endif

        ModuleView *pModuleView;

        if (proc) {
            ForEachDLinkNode(ModuleView *, pModuleView, &proc->m_moduleList) {
                if (NULL == pModuleView->m_pModule) continue;
                if (FALL_INTO(pContext->pc,
                            pModuleView->m_pModule->m_uImageBase,
                            pModuleView->m_pModule->m_uTotalSize)) {
                    kprintf("Current Module Name: %S (Full Path: %S) \n"
                        " ModuleImageBase: %x \t Offset: %x[ImageBase-PC]\n",
                        pModuleView->m_pModule->m_wszName,
                        pModuleView->m_pModule->m_wszFullPath,
                        pModuleView->m_pModule->m_uImageBase,
                        (pContext->pc - pModuleView->m_pModule->m_uImageBase));
                }
            }

            kprintf("\n");

            InProcessLinkNode *pInProcessLinkNode;
            ForEachDLinkNode(InProcessLinkNode *, pInProcessLinkNode,
                    &proc->m_threadList) {
                Thread *pThread
                    = THIS(pInProcessLinkNode, Thread, m_inProcLink);
                if (pThread->m_uUserStackBase) {
                    kprintf("Thread: %x\tThreadUserID: %x\t[%S]:\n",
                        pThread,
                        ((pThread->m_uUserStackBase &
                            ~(USER_STACK_ALIGN - 1)) + 1),
                        pThread->m_wszName);
                }
            }

            kprintf("\n");

            //all module name, baseaddress
            ForEachDLinkNode(ModuleView *, pModuleView, &proc->m_moduleList) {
                if (NULL == pModuleView->m_pModule) continue;
                kprintf("Module: %S (Full Path:%S) ModuleImageBase: %x\n",
                    pModuleView->m_pModule->m_wszName,
                    pModuleView->m_pModule->m_wszFullPath,
                    pModuleView->m_pModule->m_uImageBase
                    );
            }
        }
    }
//#endif // _RELEASE

#if (_TEST_TYPE == 2) /* Test for Daily Build */ \
    || (_TEST_TYPE == 3) || (_TEST_TYPE == 4) || (_TEST_TYPE == 5) \
    || (_TEST_TYPE == 6) || (_TEST_TYPE == 1000) /* Test for Performance */ \
    || (_TEST_TYPE == 7) /* Test for Marshalling */
    int bRebootCmd = FALSE;
    CProcess *pCurrentProcess = GetCurrentProcess();
    if (NULL != pCurrentProcess) {
        WStringBuf_<64> esbName, esbArgs;
        pCurrentProcess->GetStartInfo(&esbName, &esbArgs);
        kprintf("\n\n*********************************************\n\n");
        kprintf("%S %S : ", (wchar_t *)esbName.GetPayload(), (wchar_t *)esbArgs.GetPayload());

        if (0 == wcscmp((wchar_t *)esbName.GetPayload(), L"ezregsvr.exe")) {
            bRebootCmd = TRUE;
        }
    }

    if (IS_IN_KERNEL_MODE(pContext)) {
        kprintf("Kernel debugger!\n");
    }
    else {
        kprintf("User debugger!\n");
        if (bRebootCmd) {
            kprintf("\nTesting-System: Rebooting...\n");
            BspReboot();
        }

        if (NULL != pCurrentProcess) {
            kprintf("\nTesting-System: Terminate current process...\n");
            kprintf("\n*********************************************\n\n");
            pCurrentProcess->Kill();
        }
    }

    kprintf("\nTesting-System: Rebooting...\n");
    BspReboot();
    return true;
#endif //_TEST_TYPE == 2 || _TEST_TYPE == 7, 3, 4, 5, 6, 1000

    // Todo: optimize
    if (IS_IN_KERNEL_MODE(pContext)) {
        if (!g_bDbgStubEntry) kprintf("\nKernel debugger!\n");
//        _print = &BspWriteConsole;
        uint32_t uFlags = SaveFlagsAndCli();
        KdbHandler(vector, pContext);
        RestoreIF(uFlags);
    }
    else {
        if (!g_bDbgStubEntry) {
            kprintf("\nUser debugger!\n");
//            _print = &BspWriteConsole;
            if (IsMonitoringDebugBreak()) { // support attaching debug
                char pEventName[64];
            	wchar_t pwEventName[64];
            	pEventName[0] = 0;
            	pwEventName[0] = 0;
            	_sprintf(pEventName, "dhandlerevent-%x-%x",
            			(Int32)GetCurrentProcess() & 0x7FFFFFFF,
            			(Int32)GetCurrentThread() & 0x7FFFFFFF);
                AnsiToUnic(pwEventName, pEventName);

                IStub *pStub = NULL;
                IEvent *pEvent = new CEvent(true, 0);
				ECode ec = DzRegisterService(pwEventName, (IInterface *) pEvent, &pStub);
				if (FAILED(ec) || pStub == NULL) {
					kprintf("Create debug handler event failed.\n");
					delete pEvent;
					pEvent = NULL; // fail back to normal debug mode
				} else {
                    kprintf("\n\nPlease run \" egdb --attach %d \" to debug!\n\n",
                    		(Int32)GetCurrentProcess() & 0x7FFFFFFF);
				    WaitResult wr;
				    EventState es;
				    do {
				    	ec = pEvent->Wait(&wr, &es); // will be notified by eladbg.exe
				    } while (FAILED(ec) || wr != WaitResult_OK);
				    DzUnregisterService(pwEventName, pStub); // will delete pEvent
					return false; // Still need to send debug event back to eladbg.exe
                }
            }

#if 0 //for the autotest and man test
            while(1) {
                kprintf("\nProgram Fault. Debug Or Terminate[D/T]?");
                uint8_t c = 0;
                BspReadComPort(COMPORT_PRINT, &c);
                if (c == 'D' || c == 'd') {
                    break;
                }
                else if (c == 'T' || c == 't') {
                    CProcess *pCurrentProcess = GetCurrentProcess();
                    if (NULL != pCurrentProcess) {
                        kprintf("\nTerminate current process.\n");
                        pCurrentProcess->Kill();
                    }
                    else {
                        kprintf("\nSystem Rebooting ...\n");
                        BspReboot();
                    }
                }
            }
#endif //for the autotest and man test
            g_cDbgStubType = SelectStubType(pContext->pc);
            if (UNDEFINED_STUB != g_cDbgStubType) {
                kprintf("\n\nPlease run %s on your host machine!\n\n", \
                        ((g_cDbgStubType == ELADBG_STUB) ? "eladbgc.exe" : "gdb"));
            }
            else {
                kprintf("\n\nSorry, any user debugger stub are not present!\n\n");
            }
        }
        uint32_t uFlags = SaveFlagsAndCli();
        if (g_cDbgStubType == ELADBG_STUB) {
            ELADBG_HANDLER(vector, pContext)
        }
        else {
            UdbHandler(vector, pContext);
        }
        RestoreIF(uFlags);
    }
	return true;
#endif //ELASTOS_RC	
}
